import { defined } from './Defined'
import { DeveloperError } from './DeveloperError'
import { WebGLConstants } from './WebGLConstants'
import { HYXHMathUtil } from './HYXHMathUtil'
import { UNION_UNSIGNED_INT_ARRAY } from './HDataType'
/**
 * Constants for WebGL index datatypes.  These corresponds to the
 * <code>type</code> parameter of
 *  {@link http://www.khronos.org/opengles/sdk/docs/man/xhtml/glDrawElements.xml|drawElements}.
 *
 * @enum {number}
 */
export enum IndexDatatype {
  /**
   * 8-bit unsigned byte corresponding to <code>UNSIGNED_BYTE</code> and the type
   * of an element in <code>Uint8Array</code>.
   *
   * @type {number}
   * @constant
   */
  UNSIGNED_BYTE = WebGLConstants.UNSIGNED_BYTE,

  /**
   * 16-bit unsigned short corresponding to <code>UNSIGNED_SHORT</code> and the type
   * of an element in <code>Uint16Array</code>.
   *
   * @type {number}
   * @constant
   */
  UNSIGNED_SHORT = WebGLConstants.UNSIGNED_SHORT,

  /**
   * 32-bit unsigned int corresponding to <code>UNSIGNED_INT</code> and the type
   * of an element in <code>Uint32Array</code>.
   *
   * @type {number}
   * @constant
   */
  UNSIGNED_INT = WebGLConstants.UNSIGNED_INT,
} // end class IndexDatatype
export class IndexDatatypeUtil {
  /**
   * Returns the size, in bytes, of the corresponding datatype.
   *
   * @param {IndexDatatype} indexDatatype The index datatype to get the size of.
   * @returns {number} The size in bytes.
   *
   * @example
   * // Returns 2
   * const size = IndexDatatypeUtil.getSizeInBytes(IndexDatatypeUtil.UNSIGNED_SHORT);
   */
  public static getSizeInBytes(indexDatatype: IndexDatatype): number {
    switch (indexDatatype) {
      case IndexDatatype.UNSIGNED_BYTE:
        return Uint8Array.BYTES_PER_ELEMENT
      case IndexDatatype.UNSIGNED_SHORT:
        return Uint16Array.BYTES_PER_ELEMENT
      case IndexDatatype.UNSIGNED_INT:
        return Uint32Array.BYTES_PER_ELEMENT
    }

    //>>includeEnd('debug');
  }

  /**
   * Gets the datatype with a given size in bytes.
   *
   * @param {number} sizeInBytes The size of a single index in bytes.
   * @returns {IndexDatatype} The index datatype with the given size.
   */
  public static fromSizeInBytes(sizeInBytes: number): IndexDatatype {
    switch (sizeInBytes) {
      case 2:
        return IndexDatatype.UNSIGNED_SHORT
      case 4:
        return IndexDatatype.UNSIGNED_INT
      case 1:
        return IndexDatatype.UNSIGNED_BYTE
      //>>includeStart('debug', pragmas.debug);
      default:
        throw new DeveloperError('Size in bytes cannot be mapped to an IndexDatatype')
      //>>includeEnd('debug');
    }
  }

  /**
   * Validates that the provided index datatype is a valid {@link IndexDatatype}.
   *
   * @param {IndexDatatype} indexDatatype The index datatype to validate.
   * @returns {boolean} <code>true</code> if the provided index datatype is a valid value; otherwise, <code>false</code>.
   *
   * @example
   * if (!IndexDatatypeUtil.validate(indexDatatype)) {
   *   throw new DeveloperError('indexDatatype must be a valid value.');
   * }
   */
  public static validate(indexDatatype: IndexDatatype): boolean {
    return (
      defined(indexDatatype) &&
      (indexDatatype === IndexDatatype.UNSIGNED_BYTE ||
        indexDatatype === IndexDatatype.UNSIGNED_SHORT ||
        indexDatatype === IndexDatatype.UNSIGNED_INT)
    )
  }

  /**
   * Creates a typed array that will store indices, using either <code><Uint16Array</code>
   * or <code>Uint32Array</code> depending on the number of vertices.
   *
   * @param {number} numberOfVertices Number of vertices that the indices will reference.
   * @param {number|Array} indicesLengthOrArray Passed through to the typed array constructor.
   * @returns {Uint16Array|Uint32Array} A <code>Uint16Array</code> or <code>Uint32Array</code> constructed with <code>indicesLengthOrArray</code>.
   *
   * @example
   * this.indices = IndexDatatypeUtil.createTypedArray(positions.length / 3, numberOfIndices);
   */
  public static createTypedArray(
    numberOfVertices: number,
    indicesLengthOrArray: number | Array<number> | UNION_UNSIGNED_INT_ARRAY,
  ): UNION_UNSIGNED_INT_ARRAY {
    //>>includeStart('debug', pragmas.debug);
    if (!defined(numberOfVertices)) {
      throw new DeveloperError('numberOfVertices is required.')
    }
    //>>includeEnd('debug');

    if (numberOfVertices >= HYXHMathUtil.SIXTY_FOUR_KILOBYTES) {
      if (typeof indicesLengthOrArray === 'number') {
        return new Uint32Array(indicesLengthOrArray)
      }
      return new Uint32Array(indicesLengthOrArray)
    }
    if (typeof indicesLengthOrArray === 'number') {
      return new Uint16Array(indicesLengthOrArray)
    }
    return new Uint16Array(indicesLengthOrArray)
  }

  /**
   * Creates a typed array from a source array buffer.  The resulting typed array will store indices, using either <code><Uint16Array</code>
   * or <code>Uint32Array</code> depending on the number of vertices.
   *
   * @param {number} numberOfVertices Number of vertices that the indices will reference.
   * @param {ArrayBuffer} sourceArray Passed through to the typed array constructor.
   * @param {number} byteOffset Passed through to the typed array constructor.
   * @param {number} length Passed through to the typed array constructor.
   * @returns {Uint16Array|Uint32Array} A <code>Uint16Array</code> or <code>Uint32Array</code> constructed with <code>sourceArray</code>, <code>byteOffset</code>, and <code>length</code>.
   *
   */
  public static createTypedArrayFromArrayBuffer(
    numberOfVertices: number,
    sourceArray: ArrayBuffer,
    byteOffset: number,
    length: number,
  ): Uint16Array | Uint32Array {
    //>>includeStart('debug', pragmas.debug);
    if (!defined(numberOfVertices)) {
      throw new DeveloperError('numberOfVertices is required.')
    }
    if (!defined(sourceArray)) {
      throw new DeveloperError('sourceArray is required.')
    }
    if (!defined(byteOffset)) {
      throw new DeveloperError('byteOffset is required.')
    }
    //>>includeEnd('debug');

    if (numberOfVertices >= HYXHMathUtil.SIXTY_FOUR_KILOBYTES) {
      return new Uint32Array(sourceArray, byteOffset, length)
    }

    return new Uint16Array(sourceArray, byteOffset, length)
  }

  /**
   * Gets the {@link IndexDatatype} for the provided TypedArray instance.
   *
   * @param {Uint8Array|Uint16Array|Uint32Array} array The typed array.
   * @returns {IndexDatatype} The IndexDatatype for the provided array, or undefined if the array is not a Uint8Array, Uint16Array, or Uint32Array.
   */
  public static fromTypedArray(array: Uint8Array | Uint16Array | Uint32Array): IndexDatatype {
    if (array instanceof Uint8Array) {
      return IndexDatatype.UNSIGNED_BYTE
    }
    if (array instanceof Uint16Array) {
      return IndexDatatype.UNSIGNED_SHORT
    }
    if (array instanceof Uint32Array) {
      return IndexDatatype.UNSIGNED_INT
    }

    //>>includeStart('debug', pragmas.debug);
    throw new DeveloperError('array must be a Uint8Array, Uint16Array, or Uint32Array.')
    //>>includeEnd('debug');
  }
} // end class IndexDatatypeUtil
